﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Configuration;
using System.Threading;
using System.IO;

namespace AOYOU.Framework.Config.Core
{
    internal class Config
    {
        protected static Dictionary<string, MarshalByRefObject> _ConfigUnitHash = new Dictionary<string, MarshalByRefObject>();
        protected static IList<ConfigUnit> _ConfigUnitList = new List<ConfigUnit>();
        private static readonly object _StartLock = new object();
        private static readonly object _DicLock = new object();
        /// <summary>
        /// 开始启动配置文件标识
        /// </summary>
        private static bool _StartWatch = false;

        private static Dictionary<string, string> _ConfigFileFullPathDic;


        private const int WAITTIMEOUT = UpdateInterval / 2;
        private const int SLEEPTIME = 150;
        private const int MAX_RETRY_TIME = 3;
        private static Random ran = new Random(DateTime.Now.Millisecond);

        private const int MIN_RETRY_MS = 100;
        private const int MAX_RETRY_MS = 200;

        /// <summary>
        /// 更新间隔 单位ms 当前值 5分钟
        /// </summary>
        private const int UpdateInterval = 1000 * 60 * 5;

        /// <summary>
        /// 配置文件全路径Dictionary《文件名，全路径》
        /// </summary>
        internal static Dictionary<string, string> ConfigFileFullPathDic
        {
            get
            {
                if (_ConfigFileFullPathDic == null)
                {
                    try
                    {
                        Monitor.Enter(_DicLock);
                        if (_ConfigFileFullPathDic == null)
                        {
                            _ConfigFileFullPathDic = new Dictionary<string, string>();
                            List<string> allConfigFiles = Directory.GetFiles(Path, "*.config", SearchOption.AllDirectories).ToList();
                            foreach (string item in allConfigFiles)
                            {
                                string configFileName = System.IO.Path.GetFileName(item).ToLower();
                                string configFileFullPath = System.IO.Path.GetFullPath(item).ToLower();
                                if (_ConfigFileFullPathDic.ContainsKey(configFileName))
                                {
                                    throw new Exception(string.Format("错误：配置文件根目录下存在多个名为'{0}'的配置文件！", configFileName));
                                }
                                _ConfigFileFullPathDic.Add(configFileName, configFileFullPath);
                            }
                        }
                    }
                    catch { }
                    finally
                    {
                        Monitor.Exit(_DicLock);
                    }
                }
                return _ConfigFileFullPathDic;
            }
            set
            {
                _ConfigFileFullPathDic = value;
            }
        }

        /// <summary>
        /// 配置文件路径
        /// </summary>
        private static string _Path;

        /// <summary>
        /// 获取对应配置
        /// </summary>
        /// <param name="type">具体配置类类型</param>
        /// <returns>配置实例的透明代理</returns>
        public static ConfigUnit GetConfigUnit(Type type)
        {
            try
            {
                if (!_ConfigUnitHash.Keys.Contains(type.ToString()))
                {
                    GetConfigFromFile(Path, type);
                }
                return (ConfigUnit)_ConfigUnitHash[type.ToString()];
            }
            catch (Exception ex)
            {
                try { string.Format("获取配置信息出现异常，ConfigType={0}，错误信息：{1}。{2}", type.FullName, ex.Message, ex.StackTrace); }
                catch { }
                return null;
            }
        }

        private static void GetConfigFromFile(string configPath, Type configType)
        {
            try
            {
                Monitor.Enter(_StartLock);
                #region 将配置实例加入配置列表

                //如果有其他线程将配置加载到内存了，那么不需要再次加载配置
                if (_ConfigUnitHash.Keys.Contains(configType.ToString()))
                {
                    return;
                }

                Type baseType = typeof(ConfigUnit);
                if (configType.IsSubclassOf(baseType))
                {
                    IList<ConfigUnit> _ConfigUnitList_temp = new List<ConfigUnit>();
                    foreach (ConfigUnit cu in _ConfigUnitList)
                    {
                        if (cu.GetType().FullName != configType.FullName)
                        {
                            _ConfigUnitList_temp.Add(cu);
                        }
                    }
                    _ConfigUnitList.Clear();
                    _ConfigUnitList = _ConfigUnitList_temp;
                    _ConfigUnitList.Add((ConfigUnit)Activator.CreateInstance(configType));
                }
                else
                {
                    throw new Exception(string.Format("传入参数配置类型‘{0}’不是配置基类‘{1}’的子类！", configType.FullName, baseType.FullName));
                }

                #endregion

                //从本地配置文件加载配置到内存

                //=====开始加载配置{0}（本地文件）=====

                OnFileUpdate(configPath);

                //=====完成加载配置{0}（本地文件）=====

                //启动线程，每隔5分钟检查一下文件修改时间是否改变，确定是否重新加载配置；保证配置修改后会生效
                if (!_StartWatch)
                {
                    System.Threading.Thread t = new System.Threading.Thread(FileSpool);
                    t.Start(configPath);
                    _StartWatch = true;
                }

            }
            catch (Exception ex)
            {
                string.Format("GetConfigFromFile错误 将无法获取对应的数据！错误信息：{0}，{1}", ex.Message, ex.StackTrace);
            }
            finally
            {
                Monitor.Exit(_StartLock);
            }
        }

        /// <summary>
        /// 配置文件路径
        /// </summary>
        public static string Path
        {
            get
            {
                if (_Path == null)
                {
                    _Path = ConfigurationManager.AppSettings["Framework.Config.Path"];
                    if (string.IsNullOrEmpty(_Path))
                    {
                        if (HttpContext.Current != null)
                        {
                            //Web工程按照运维准入标准，将Conf目录定位到应用程序域所在目录的Conf子目录
                            string _BasePath = System.IO.Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory);
                            _Path = _BasePath + "Conf";
                        }
                        else
                        {
                            //非Web工程按照运维准入标准，将Conf目录定位到应用程序域所在目录并列的Conf目录
                            string _BasePath = System.IO.Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + @"\..\");
                            _Path = _BasePath + "Conf";
                        }
                    }
                }
                return _Path;
            }
            set
            {
                _Path = value;
            }
        }

        /// <summary>
        /// 定时轮巡本地配置文件，以便将最新配置加载到内存中
        /// </summary>
        /// <param name="para">配置文件所在路径</param>
        private static void FileSpool(object para)
        {
            while (true)
            {
                try
                {
                    string path = para.ToString();

                    //重新加载<配置文件名，配置文件全路径>字典
                    ConfigFileFullPathDic = null;
                    Dictionary<string, string> temp = ConfigFileFullPathDic;
                    temp = null;

                    //读取当前文件修改时间，并存入哈希表
                    DirectoryInfo di = new DirectoryInfo(path);
                    Dictionary<string, FileInfo> fileHash = di.GetFiles("*.config", SearchOption.AllDirectories).ToDictionary(p => p.Name);

                    while (true)
                    {
                        try
                        {
                            //轮巡时间间隔
                            System.Threading.Thread.Sleep(UpdateInterval);

                            di = new DirectoryInfo(path);
                            FileInfo[] files = di.GetFiles("*.config", SearchOption.AllDirectories);
                            foreach (FileInfo fi in files)
                            {
                                //配置文件被修改，重新加载配置
                                if (fileHash.ContainsKey(fi.Name)
                                    && fi.LastWriteTime != fileHash[fi.Name].LastWriteTime)
                                {

                                    OnFileUpdate(path);
                                    fileHash = di.GetFiles("*.config", SearchOption.AllDirectories).ToDictionary(p => p.Name);
                                    break;
                                }
                                //如果fileHash中不包含这个文件，说明这个配置是新加进来的；
                                //也需要加入到监控的文件列表中，因此，重新获取Hash；
                                else if (!fileHash.ContainsKey(fi.Name))
                                {
                                    fileHash = di.GetFiles("*.config", SearchOption.AllDirectories).ToDictionary(p => p.Name);
                                }
                            }
                        }
                        catch (Exception ee)
                        {
                            //轮询时发生异常（本地文件），异常信息：{0}；{1}
                        }
                    }
                }
                catch (Exception ee)
                {
                    //轮询时发生异常（本地文件），可能无法读取配置文件FileInfo,程序将重新启动轮询。异常信息：{0}；{1
                }
                Thread.Sleep(ran.Next(MIN_RETRY_MS, MAX_RETRY_MS));
            }
        }

        /// <summary>
        /// 将_ConfigUnitList中的每个ConfigUnit重新加载一次，并更新到_ConfigUnitHash中；
        /// </summary>
        /// <param name="status">配置文件所在目录（Config.Path）</param>
        private static void OnFileUpdate(object status)
        {
            //更新_ConfigUnitList
            for (int i = 0; i < _ConfigUnitList.Count; i++)
            {
                int triedTimes = 0;
                while (triedTimes < MAX_RETRY_TIME && !UpDateOneConfig(status, i))
                {
                    triedTimes++;
                    Thread.Sleep(ran.Next(MIN_RETRY_MS, MAX_RETRY_MS));
                    if (triedTimes < MAX_RETRY_TIME)
                    {
                        string.Format("第{1}次重试,最多重试{2}次，开始加载配置(本地文件){0}");
                         
                    }
                }
            }

            //更新_ConfigUnitHash，将配置实例的透明代理放到hash中
            foreach (ConfigUnit unit in _ConfigUnitList)
            {
                #region 不再使用透明代理控制访问权限
                ////透明代理，AOP
                //MarshalByRefObject mobj = unit;
                //AspectProxy realProxy = new AspectProxy(unit.GetType(), mobj);
                //MarshalByRefObject refObj = (MarshalByRefObject)realProxy.GetTransparentProxy();
                //if (!_ConfigUnitHash.Keys.Contains(unit.GetType().ToString()))
                //{
                //    _ConfigUnitHash.Add(unit.GetType().ToString(), refObj);
                //}
                //else
                //{
                //    _ConfigUnitHash[unit.GetType().ToString()] = refObj;
                //}
                #endregion
                if (!_ConfigUnitHash.Keys.Contains(unit.GetType().ToString()))
                {
                    _ConfigUnitHash.Add(unit.GetType().ToString(), unit);
                }
                else
                {
                    _ConfigUnitHash[unit.GetType().ToString()] = unit;
                }
            }
        }

        /// <summary>
        /// 加载一个配置到内存
        /// </summary>
        /// <param name="status">路径</param>
        /// <param name="i">数组下标</param>
        /// <returns>是否加载成功</returns>
        private static bool UpDateOneConfig(object status, int i)
        {
            bool retVal = true;
            try
            {
                _ConfigUnitList[i] = _ConfigUnitList[i].OnUpdate(status);
            }
            catch (Exception e)
            {
                retVal = false;
            }
            return retVal;
        }

    }
}
